<script setup lang="ts">
import type {
	CurlToJSONResponse,
	INodeUi,
	INodeUpdatePropertiesInformation,
	IUpdateInformation,
} from '@/Interface';
import type {
	INodeCredentialDescription,
	INodeParameters,
	NodeConnectionType,
	NodeParameterValue,
} from 'n8n-workflow';
import { NodeConnectionTypes, NodeHelpers, deepCopy, isCommunityPackageName } from 'n8n-workflow';
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';

import { BASE_NODE_SURVEY_URL, VIEWS } from '@/app/constants';

import NDVSubConnections from '@/features/ndv/panel/components/NDVSubConnections.vue';
import NodeCredentials from '@/features/credentials/components/NodeCredentials.vue';
import NodeSettingsHeader from './NodeSettingsHeader.vue';
import NodeSettingsTabs from './NodeSettingsTabs.vue';
import NodeWebhooks from './NodeWebhooks.vue';
import ParameterInputList from '@/features/ndv/parameters/components/ParameterInputList.vue';
import get from 'lodash/get';

import ExperimentalEmbeddedNdvHeader from '@/features/workflows/canvas/experimental/components/ExperimentalEmbeddedNdvHeader.vue';
import FreeAiCreditsCallout from '@/app/components/FreeAiCreditsCallout.vue';
import NodeActionsList from '@/app/components/NodeActionsList.vue';
import NodeSettingsInvalidNodeWarning from './NodeSettingsInvalidNodeWarning.vue';
import { useExternalHooks } from '@/app/composables/useExternalHooks';
import { useInstalledCommunityPackage } from '@/features/settings/communityNodes/composables/useInstalledCommunityPackage';
import { useNodeCredentialOptions } from '@/features/credentials/composables/useNodeCredentialOptions';
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
import { useNodeSettingsParameters } from '@/features/ndv/settings/composables/useNodeSettingsParameters';
import { useTelemetry } from '@/app/composables/useTelemetry';
import { importCurlEventBus } from '@/app/event-bus';
import { ndvEventBus } from '@/features/ndv/shared/ndv.eventBus';
import NodeStorageLimitCallout from '@/features/core/dataTable/components/NodeStorageLimitCallout.vue';
import NodeTitle from '@/app/components/NodeTitle.vue';
import { RenameNodeCommand } from '@/app/models/history';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useHistoryStore } from '@/app/stores/history.store';
import { useNDVStore } from '@/features/ndv/shared/ndv.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useUsersStore } from '@/features/settings/users/users.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import type { NodeSettingsTab } from '@/app/types/nodeSettings';
import { getNodeIconSource } from '@/app/utils/nodeIcon';
import {
	collectParametersByTab,
	collectSettings,
	createCommonNodeSettings,
	getNodeSettingsInitialValues,
	nameIsParameter,
} from '../../shared/ndv.utils';
import { useI18n } from '@n8n/i18n';
import type { EventBus } from '@n8n/utils/event-bus';
import { useResizeObserver } from '@vueuse/core';
import CommunityNodeFooter from '@/features/settings/communityNodes/components/nodeCreator/CommunityNodeFooter.vue';
import CommunityNodeUpdateInfo from '@/features/settings/communityNodes/components/nodeCreator/CommunityNodeUpdateInfo.vue';
import NodeExecuteButton from '@/app/components/NodeExecuteButton.vue';

import { N8nBlockUi, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system';
import { useRoute } from 'vue-router';
import { useSettingsStore } from '@/app/stores/settings.store';
import { injectWorkflowState } from '@/app/composables/useWorkflowState';
import { ProjectTypes } from '@/features/collaboration/projects/projects.types';

const props = withDefaults(
	defineProps<{
		eventBus?: EventBus;
		dragging: boolean;
		pushRef: string;
		readOnly: boolean;
		foreignCredentials: string[];
		blockUI: boolean;
		executable: boolean;
		inputSize?: number;
		activeNode?: INodeUi;
		isEmbeddedInCanvas?: boolean;
		subTitle?: string;
		extraTabsClassName?: string;
		extraParameterWrapperClassName?: string;
		isNdvV2?: boolean;
		hideExecute?: boolean;
		hideDocs?: boolean;
		hideSubConnections?: boolean;
	}>(),
	{
		inputSize: 0,
		activeNode: undefined,
		isEmbeddedInCanvas: false,
		subTitle: undefined,
		isNdvV2: false,
		hideExecute: false,
		hideDocs: true,
		hideSubConnections: false,
	},
);

const emit = defineEmits<{
	stopExecution: [];
	valueChanged: [value: IUpdateInformation];
	switchSelectedNode: [nodeName: string];
	openConnectionNodeCreator: [
		nodeName: string,
		connectionType: NodeConnectionType,
		connectionIndex?: number,
	];
	activate: [];
	execute: [];
	captureWheelBody: [WheelEvent];
	dblclickHeader: [MouseEvent];
}>();

const slots = defineSlots<{ actions?: {} }>();

const nodeValues = ref<INodeParameters>(getNodeSettingsInitialValues());

const nodeTypesStore = useNodeTypesStore();
const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore();
const workflowState = injectWorkflowState();
const credentialsStore = useCredentialsStore();
const historyStore = useHistoryStore();

const telemetry = useTelemetry();
const nodeHelpers = useNodeHelpers();
const externalHooks = useExternalHooks();
const i18n = useI18n();
const route = useRoute();
const nodeSettingsParameters = useNodeSettingsParameters();

const nodeParameterWrapper = useTemplateRef('nodeParameterWrapper');
const shouldShowStaticScrollbar = ref(false);

if (props.isEmbeddedInCanvas) {
	useResizeObserver(nodeParameterWrapper, () => {
		shouldShowStaticScrollbar.value =
			(nodeParameterWrapper.value?.scrollHeight ?? 0) >
			(nodeParameterWrapper.value?.offsetHeight ?? 0);
	});
}

const nodeValid = ref(true);
const openPanel = ref<NodeSettingsTab>('params');

// Used to prevent nodeValues from being overwritten by defaults on reopening ndv
const nodeValuesInitialized = ref(false);

const hiddenIssuesInputs = ref<string[]>([]);
const subConnections = ref<InstanceType<typeof NDVSubConnections> | null>(null);

const isDemoRoute = computed(() => route?.name === VIEWS.DEMO);
const { isPreviewMode } = useSettingsStore();
const isDemoPreview = computed(() => isDemoRoute.value && isPreviewMode);
const currentWorkflow = computed(
	() => workflowsStore.getWorkflowById(workflowsStore.workflowObject.id), // @TODO check if we actually need workflowObject here
);
const hasForeignCredential = computed(() => props.foreignCredentials.length > 0);
const isHomeProjectTeam = computed(
	() => currentWorkflow.value?.homeProject?.type === ProjectTypes.Team,
);
const isReadOnly = computed(
	() => props.readOnly || (hasForeignCredential.value && !isHomeProjectTeam.value),
);
const node = computed(() => props.activeNode ?? ndvStore.activeNode);

const nodeType = computed(() =>
	node.value ? nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion) : null,
);

const { areAllCredentialsSet } = useNodeCredentialOptions(node, nodeType, '');

const nodeTypeName = computed(() => node.value?.type);
const { installedPackage, isUpdateCheckAvailable } = useInstalledCommunityPackage(nodeTypeName);

const isTriggerNode = computed(() => !!node.value && nodeTypesStore.isTriggerNode(node.value.type));

const isToolNode = computed(() => !!node.value && nodeTypesStore.isToolNode(node.value.type));

const isExecutable = computed(() =>
	nodeHelpers.isNodeExecutable(node.value, props.executable, props.foreignCredentials),
);

const nodeTypeVersions = computed(() => {
	if (!node.value) return [];
	return nodeTypesStore.getNodeVersions(node.value.type);
});

const latestVersion = computed(() => Math.max(...nodeTypeVersions.value));

const isLatestNodeVersion = computed(
	() => !node.value?.typeVersion || latestVersion.value === node.value.typeVersion,
);

const executeButtonTooltip = computed(() => {
	if (
		node.value &&
		isLatestNodeVersion.value &&
		props.inputSize > 1 &&
		!nodeHelpers.isSingleExecution(node.value.type, node.value.parameters)
	) {
		return i18n.baseText('nodeSettings.executeButtonTooltip.times', {
			interpolate: { inputSize: props.inputSize },
		});
	}
	return '';
});

const nodeVersionTag = computed(() => {
	if (!nodeType.value || nodeType.value.hidden) {
		return i18n.baseText('nodeSettings.deprecated');
	}

	if (isLatestNodeVersion.value) {
		return i18n.baseText('nodeSettings.latest');
	}

	return i18n.baseText('nodeSettings.latestVersion', {
		interpolate: { version: latestVersion.value.toString() },
	});
});

const parameters = computed(() => {
	if (nodeType.value === null) {
		return [];
	}

	return nodeType.value?.properties ?? [];
});

const parametersByTab = computed(() =>
	collectParametersByTab(parameters.value, props.isEmbeddedInCanvas),
);

const isDisplayingCredentials = computed(
	() =>
		credentialsStore
			.getCredentialTypesNodeDescriptions('', nodeType.value)
			.filter((credentialTypeDescription) => displayCredentials(credentialTypeDescription)).length >
		0,
);

const showNoParametersNotice = computed(
	() =>
		!isDisplayingCredentials.value &&
		(parametersByTab.value.params ?? []).filter((item) => item.type !== 'notice').length === 0,
);

const outputPanelEditMode = computed(() => ndvStore.outputPanelEditMode);

const isCommunityNode = computed(() => !!node.value && isCommunityPackageName(node.value.type));
const packageName = computed(() => node.value?.type.split('.')[0] ?? '');

const usedCredentials = computed(() =>
	Object.values(workflowsStore.usedCredentials).filter((credential) =>
		Object.values(node.value?.credentials || []).find(
			(nodeCredential) => nodeCredential.id === credential.id,
		),
	),
);

const credentialOwnerName = computed(() => {
	const credential = usedCredentials.value
		? Object.values(usedCredentials.value).find(
				(credential) => credential.id === props.foreignCredentials[0],
			)
		: undefined;

	return credentialsStore.getCredentialOwnerName(credential);
});

const featureRequestUrl = computed(() => {
	if (!nodeType.value) {
		return '';
	}
	return `${BASE_NODE_SURVEY_URL}${nodeType.value.name}`;
});

const hasOutputConnection = computed(() => {
	if (!node.value) return false;
	const outgoingConnections = workflowsStore.outgoingConnectionsByNodeName(node.value.name);

	// Check if there's at-least one output connection
	return (Object.values(outgoingConnections)?.[0]?.[0] ?? []).length > 0;
});

const valueChanged = (parameterData: IUpdateInformation) => {
	let newValue: NodeParameterValue;

	if (parameterData.hasOwnProperty('value')) {
		// New value is given
		newValue = parameterData.value as string | number;
	} else {
		// Get new value from nodeData where it is set already
		newValue = get(nodeValues.value, parameterData.name) as NodeParameterValue;
	}
	// Save the node name before we commit the change because
	// we need the old name to rename the node properly
	const nodeNameBefore = parameterData.node || node.value?.name;

	if (!nodeNameBefore) {
		return;
	}

	const _node = workflowsStore.getNodeByName(nodeNameBefore);

	if (_node === null) {
		return;
	}

	if (parameterData.name === 'name') {
		// Name of node changed so we have to set also the new node name as active

		// Update happens in NodeView so emit event
		const sendData = {
			value: newValue,
			oldValue: nodeNameBefore,
			name: parameterData.name,
		};
		emit('valueChanged', sendData);
	} else if (parameterData.name === 'parameters') {
		const _nodeType = nodeTypesStore.getNodeType(_node.type, _node.typeVersion);
		if (!_nodeType) {
			return;
		}

		// Get only the parameters which are different to the defaults
		let nodeParameters = NodeHelpers.getNodeParameters(
			_nodeType.properties,
			_node.parameters,
			false,
			false,
			_node,
			_nodeType,
		);

		const oldNodeParameters = Object.assign({}, nodeParameters);

		// Copy the data because it is the data of vuex so make sure that
		// we do not edit it directly
		nodeParameters = deepCopy(nodeParameters);

		if (parameterData.value && typeof parameterData.value === 'object') {
			for (const [parameterName, parameterValue] of Object.entries(parameterData.value)) {
				newValue = parameterValue;

				const parameterPath = nodeSettingsParameters.updateParameterByPath(
					parameterName,
					newValue,
					nodeParameters,
					_nodeType,
					_node.typeVersion,
				);

				void externalHooks.run('nodeSettings.valueChanged', {
					parameterPath,
					newValue,
					parameters: parameters.value,
					oldNodeParameters,
				});
			}
		}

		// Get the parameters with the now new defaults according to the
		// from the user actually defined parameters
		nodeParameters = NodeHelpers.getNodeParameters(
			_nodeType.properties,
			nodeParameters as INodeParameters,
			true,
			false,
			_node,
			_nodeType,
		);

		for (const key of Object.keys(nodeParameters as object)) {
			if (nodeParameters?.[key] !== null && nodeParameters?.[key] !== undefined) {
				nodeSettingsParameters.setValue(
					nodeValues,
					`parameters.${key}`,
					nodeParameters[key] as string,
				);
			}
		}

		if (nodeParameters) {
			const updateInformation: IUpdateInformation = {
				name: _node.name,
				value: nodeParameters,
			};

			workflowState.setNodeParameters(updateInformation);

			nodeHelpers.updateNodeParameterIssuesByName(_node.name);
			nodeHelpers.updateNodeCredentialIssuesByName(_node.name);
		}
	} else if (nameIsParameter(parameterData)) {
		// A node parameter changed
		nodeSettingsParameters.updateNodeParameter(
			nodeValues,
			parameterData,
			newValue,
			_node,
			isToolNode.value,
		);
	} else {
		// A property on the node itself changed

		// Update data in settings
		nodeValues.value = {
			...nodeValues.value,
			[parameterData.name]: newValue,
		};

		// Update data in vuex
		const updateInformation = {
			name: _node.name,
			key: parameterData.name,
			value: newValue,
		};

		workflowState.setNodeValue(updateInformation);
	}
};

const setHttpNodeParameters = (parameters: CurlToJSONResponse) => {
	try {
		valueChanged({
			node: node.value?.name,
			name: 'parameters',
			value: parameters as unknown as INodeParameters,
		});
	} catch {}
};

const onSwitchSelectedNode = (node: string) => {
	emit('switchSelectedNode', node);
};

const onOpenConnectionNodeCreator = (
	nodeName: string,
	connectionType: NodeConnectionType,
	connectionIndex: number = 0,
) => {
	emit('openConnectionNodeCreator', nodeName, connectionType, connectionIndex);
};

const populateHiddenIssuesSet = () => {
	if (!node.value || !workflowsStore.isNodePristine(node.value.name)) return;
	hiddenIssuesInputs.value.push('credentials');
	parametersByTab.value.params.forEach((parameter) => {
		hiddenIssuesInputs.value.push(parameter.name);
	});
	workflowsStore.setNodePristine(node.value.name, false);
};

const nodeSettings = computed(() =>
	createCommonNodeSettings(isExecutable.value, isToolNode.value, i18n.baseText.bind(i18n)),
);

const iconSource = computed(() =>
	getNodeIconSource(nodeType.value ?? node.value?.type, node.value ?? null),
);

const onParameterBlur = (parameterName: string) => {
	hiddenIssuesInputs.value = hiddenIssuesInputs.value.filter((name) => name !== parameterName);
};

const onWorkflowActivate = () => {
	hiddenIssuesInputs.value = [];
	emit('activate');
};

const onNodeExecute = () => {
	hiddenIssuesInputs.value = [];
	subConnections.value?.showNodeInputsIssues();
	emit('execute');
};

const credentialSelected = (updateInformation: INodeUpdatePropertiesInformation) => {
	// Update the values on the node
	workflowState.updateNodeProperties(updateInformation);

	const node = workflowsStore.getNodeByName(updateInformation.name);

	if (node) {
		// Update the issues
		nodeHelpers.updateNodeCredentialIssues(node);
	}

	void externalHooks.run('nodeSettings.credentialSelected', { updateInformation });
};

const nameChanged = (name: string) => {
	if (node.value) {
		historyStore.pushCommandToUndo(new RenameNodeCommand(node.value.name, name, Date.now()));
	}
	valueChanged({
		value: name,
		name: 'name',
	});
};

const setNodeValues = () => {
	// No node selected
	if (!node.value) {
		nodeValuesInitialized.value = true;
		return;
	}

	if (nodeType.value !== null) {
		nodeValid.value = true;
		nodeValues.value = collectSettings(node.value, nodeSettings.value);
	} else {
		nodeValid.value = false;
	}

	nodeValuesInitialized.value = true;
};

const onStopExecution = () => {
	emit('stopExecution');
};

const openSettings = () => {
	openPanel.value = 'settings';
};

const onTabSelect = (tab: NodeSettingsTab) => {
	openPanel.value = tab;
};

const onFeatureRequestClick = () => {
	window.open(featureRequestUrl.value, '_blank');
	if (node.value) {
		telemetry.track('User clicked ndv link', {
			node_type: node.value.type,
			workflow_id: workflowsStore.workflowId,
			push_ref: props.pushRef,
			pane: NodeConnectionTypes.Main,
			type: 'i-wish-this-node-would',
		});
	}
};

watch(node, () => {
	setNodeValues();
});

onMounted(async () => {
	populateHiddenIssuesSet();
	setNodeValues();
	props.eventBus?.on('openSettings', openSettings);
	if (node.value !== null) {
		nodeHelpers.updateNodeParameterIssues(node.value, nodeType.value);
	}
	importCurlEventBus.on('setHttpNodeParameters', setHttpNodeParameters);
	ndvEventBus.on('updateParameterValue', valueChanged);
});

onBeforeUnmount(() => {
	props.eventBus?.off('openSettings', openSettings);
	importCurlEventBus.off('setHttpNodeParameters', setHttpNodeParameters);
	ndvEventBus.off('updateParameterValue', valueChanged);
});

function displayCredentials(credentialTypeDescription: INodeCredentialDescription): boolean {
	if (credentialTypeDescription.displayOptions === undefined) {
		// If it is not defined no need to do a proper check
		return true;
	}

	return (
		!!node.value &&
		nodeHelpers.displayParameter(node.value.parameters, credentialTypeDescription, '', node.value)
	);
}

function handleSelectAction(params: INodeParameters) {
	for (const [key, value] of Object.entries(params)) {
		valueChanged({ name: `parameters.${key}`, value });
	}

	if (isDisplayingCredentials.value && !areAllCredentialsSet.value) {
		onTabSelect('credential');
		return;
	}

	if (parametersByTab.value.params.length > 0) {
		onTabSelect('params');
	}
}
</script>

<template>
	<div
		:class="{
			'node-settings': true,
			dragging: dragging,
			embedded: props.isEmbeddedInCanvas,
		}"
		:data-has-output-connection="hasOutputConnection"
		@keydown.stop
	>
		<ExperimentalEmbeddedNdvHeader
			v-if="isEmbeddedInCanvas && node"
			:node="node"
			:selected-tab="openPanel"
			:read-only="readOnly"
			:node-type="nodeType"
			:push-ref="pushRef"
			:sub-title="subTitle"
			:extra-tabs-class-name="extraTabsClassName"
			:include-action="parametersByTab.action.length > 0"
			:include-credential="isDisplayingCredentials"
			:has-credential-issue="!areAllCredentialsSet"
			@name-changed="nameChanged"
			@tab-changed="onTabSelect"
			@dblclick-title="emit('dblclickHeader', $event)"
		>
			<template #actions>
				<slot name="actions" />
			</template>
		</ExperimentalEmbeddedNdvHeader>
		<div v-else-if="!isNdvV2" :class="$style.header">
			<div class="header-side-menu">
				<NodeTitle
					v-if="node"
					class="node-name"
					:model-value="node.name"
					:icon-source="iconSource"
					:read-only="isReadOnly"
					:node-type="nodeType"
					@update:model-value="nameChanged"
				/>
				<NodeExecuteButton
					v-if="isExecutable && !blockUI && node && nodeValid"
					data-test-id="node-execute-button"
					:node-name="node.name"
					:disabled="outputPanelEditMode.enabled && !isTriggerNode"
					:tooltip="executeButtonTooltip"
					size="small"
					telemetry-source="parameters"
					@execute="onNodeExecute"
					@stop-execution="onStopExecution"
					@value-changed="valueChanged"
				/>
			</div>
			<NodeSettingsTabs
				v-if="node && nodeValid"
				:model-value="openPanel"
				:node-type="nodeType"
				:push-ref="pushRef"
				@update:model-value="onTabSelect"
			/>
		</div>
		<NodeSettingsHeader
			v-else-if="node && nodeValid"
			:selected-tab="openPanel"
			:node-name="node.name"
			:node-type="nodeType"
			:execute-button-tooltip="executeButtonTooltip"
			:hide-execute="props.hideExecute || !isExecutable || blockUI || !node || !nodeValid"
			:disable-execute="outputPanelEditMode.enabled && !isTriggerNode"
			:hide-tabs="!nodeValid"
			:hide-docs="props.hideDocs"
			:push-ref="pushRef"
			@execute="onNodeExecute"
			@stop-execution="onStopExecution"
			@value-changed="valueChanged"
			@tab-changed="onTabSelect"
		/>

		<NodeSettingsInvalidNodeWarning
			v-if="node && !nodeValid"
			:node="node"
			:preview-mode="isDemoPreview"
		/>

		<div
			v-if="node && nodeValid"
			ref="nodeParameterWrapper"
			:class="[
				'node-parameters-wrapper',
				shouldShowStaticScrollbar ? 'with-static-scrollbar' : '',
				{ 'ndv-v2': isNdvV2 },
				extraParameterWrapperClassName ?? '',
			]"
			data-test-id="node-parameters"
			@wheel.capture="emit('captureWheelBody', $event)"
		>
			<N8nNotice
				v-if="hasForeignCredential && !isHomeProjectTeam"
				:content="
					i18n.baseText('nodeSettings.hasForeignCredential', {
						interpolate: { owner: credentialOwnerName },
					})
				"
			/>
			<FreeAiCreditsCallout />
			<NodeStorageLimitCallout />
			<NodeActionsList
				v-if="openPanel === 'action'"
				class="action-tab"
				:node="node"
				@action-selected="handleSelectAction"
			/>
			<NodeCredentials
				v-if="openPanel === 'credential'"
				:node="node"
				:readonly="isReadOnly"
				:show-all="true"
				:hide-issues="hiddenIssuesInputs.includes('credentials')"
				@credential-selected="credentialSelected"
				@value-changed="valueChanged"
				@blur="onParameterBlur"
			/>
			<div v-show="openPanel === 'params'">
				<NodeWebhooks :node="node" :node-type-description="nodeType" />

				<ParameterInputList
					v-if="nodeValuesInitialized"
					:parameters="parametersByTab.params"
					:hide-delete="true"
					:node-values="nodeValues"
					:is-read-only="isReadOnly"
					:hidden-issues-inputs="hiddenIssuesInputs"
					path="parameters"
					:node="props.activeNode"
					@value-changed="valueChanged"
					@activate="onWorkflowActivate"
					@parameter-blur="onParameterBlur"
				>
					<NodeCredentials
						v-if="!isEmbeddedInCanvas && !isDemoPreview"
						:node="node"
						:readonly="isReadOnly"
						:show-all="true"
						:hide-issues="hiddenIssuesInputs.includes('credentials')"
						@credential-selected="credentialSelected"
						@value-changed="valueChanged"
						@blur="onParameterBlur"
					/>
				</ParameterInputList>
				<div v-if="showNoParametersNotice" class="no-parameters">
					<N8nText>
						{{ i18n.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
					</N8nText>
				</div>

				<div
					v-if="nodeHelpers.isCustomApiCallSelected(nodeValues)"
					class="parameter-item parameter-notice"
					data-test-id="node-parameters-http-notice"
				>
					<N8nNotice
						:content="
							i18n.baseText('nodeSettings.useTheHttpRequestNode', {
								interpolate: { nodeTypeDisplayName: nodeType?.displayName ?? '' },
							})
						"
					/>
				</div>
			</div>
			<div v-show="openPanel === 'settings'">
				<CommunityNodeUpdateInfo
					v-if="isUpdateCheckAvailable && installedPackage?.updateAvailable"
					data-test-id="update-available"
					:package-name="packageName"
					style="margin-top: var(--spacing--sm)"
					source="node settings"
				/>
				<ParameterInputList
					:parameters="parametersByTab.settings"
					:node-values="nodeValues"
					:is-read-only="isReadOnly"
					:hide-delete="true"
					:hidden-issues-inputs="hiddenIssuesInputs"
					path="parameters"
					@value-changed="valueChanged"
					@parameter-blur="onParameterBlur"
				/>
				<ParameterInputList
					:parameters="nodeSettings"
					:hide-delete="true"
					:node-values="nodeValues"
					:is-read-only="isReadOnly"
					:hidden-issues-inputs="hiddenIssuesInputs"
					path=""
					@value-changed="valueChanged"
					@parameter-blur="onParameterBlur"
				/>
				<div class="node-version" data-test-id="node-version">
					{{
						i18n.baseText('nodeSettings.nodeVersion', {
							interpolate: {
								node: nodeType?.displayName as string,
								version: (node.typeVersion ?? latestVersion).toString(),
							},
						})
					}}
					<span>({{ nodeVersionTag }})</span>
				</div>
			</div>
			<div
				v-if="isNdvV2 && featureRequestUrl && !isEmbeddedInCanvas"
				:class="$style.featureRequest"
			>
				<a target="_blank" @click="onFeatureRequestClick">
					<N8nIcon icon="lightbulb" />
					{{ i18n.baseText('ndv.featureRequest') }}
				</a>
			</div>
		</div>
		<NDVSubConnections
			v-if="node && !hideSubConnections"
			ref="subConnections"
			:root-node="node"
			@switch-selected-node="onSwitchSelectedNode"
			@open-connection-node-creator="onOpenConnectionNodeCreator"
		/>
		<N8nBlockUi
			:show="blockUI"
			:class="{
				[$style.uiBlockerNdvV2]: isNdvV2,
			}"
		/>
		<CommunityNodeFooter
			v-if="openPanel === 'settings' && isCommunityNode"
			:package-name="packageName"
			:show-manage="useUsersStore().isInstanceOwner"
		/>
	</div>
</template>

<style lang="scss" module>
.header {
	background-color: var(--ndv--header--color);
}

.featureRequest {
	margin-top: auto;
	align-self: center;

	a {
		display: inline-flex;
		align-items: center;
		gap: var(--spacing--4xs);
		margin-top: var(--spacing--xl);

		font-size: var(--font-size--2xs);
		font-weight: var(--font-weight--bold);
		color: var(--color--text--tint-1);
	}
}

.uiBlockerNdvV2 {
	border-radius: 0;
}
</style>

<style lang="scss" scoped>
.node-settings {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background-color: var(--ndv--background--color);
	height: 100%;
	width: 100%;

	.no-parameters {
		margin-top: var(--spacing--xs);
	}

	.header-side-menu {
		padding: var(--spacing--sm) var(--spacing--sm) var(--spacing--sm) var(--spacing--sm);
		font-size: var(--font-size--lg);
		display: flex;
		justify-content: space-between;

		.node-name {
			padding-top: var(--spacing--5xs);
			margin-right: var(--spacing--sm);
		}
	}

	.node-parameters-wrapper {
		display: flex;
		flex-direction: column;
		overflow-y: auto;
		padding: 0 var(--spacing--md) var(--spacing--lg) var(--spacing--md);
		flex-grow: 1;

		&.ndv-v2 {
			padding: 0 var(--spacing--sm) var(--spacing--lg) var(--spacing--sm);
		}
	}

	&.embedded .node-parameters-wrapper {
		padding: 0 var(--spacing--xs) var(--spacing--xs) var(--spacing--xs);

		&:has(.action-tab) {
			padding: 0 0 var(--spacing--xs) 0;
		}
	}

	&.embedded .node-parameters-wrapper.with-static-scrollbar {
		padding: 0 var(--spacing--4xs) var(--spacing--xs) var(--spacing--xs);

		&:has(.action-tab) {
			padding: 0 0 var(--spacing--xs) 0;
		}

		@supports not (selector(::-webkit-scrollbar)) {
			scrollbar-width: thin;
		}
		@supports selector(::-webkit-scrollbar) {
			&::-webkit-scrollbar {
				width: var(--spacing--2xs);
			}
			&::-webkit-scrollbar-thumb {
				border-radius: var(--spacing--2xs);
				background: var(--color--foreground--shade-1);
				border: var(--spacing--5xs) solid var(--color--background--light-3);
			}
		}
	}

	&.dragging {
		border-color: var(--color--primary);
		box-shadow: 0 6px 16px rgba(255, 74, 51, 0.15);
	}
}

.parameter-content {
	font-size: 0.9em;
	margin-right: -15px;
	margin-left: -15px;
	input {
		width: calc(100% - 35px);
		padding: 5px;
	}
	select {
		width: calc(100% - 20px);
		padding: 5px;
	}

	&:before {
		display: table;
		content: ' ';
		position: relative;
		box-sizing: border-box;
		clear: both;
	}
}

.parameter-wrapper {
	padding: 0 1em;
}

.color-reset-button-wrapper {
	position: relative;
}
.color-reset-button {
	position: absolute;
	right: 7px;
	top: -25px;
}

.node-version {
	border-top: var(--border);
	font-size: var(--font-size--xs);
	font-size: var(--font-size--2xs);
	padding: var(--spacing--xs) 0 var(--spacing--2xs) 0;
	color: var(--color--text--tint-1);
}

.parameter-value {
	input.expression {
		border-style: dashed;
		border-color: #ff9600;
		display: inline-block;
		position: relative;
		width: 100%;
		box-sizing: border-box;
		background-color: #793300;
	}
}
</style>

<style lang="scss">
// Hide notice(.ndv-connection-hint-notice) warning when node has output connection
[data-has-output-connection='true'] .ndv-connection-hint-notice {
	display: none;
}
</style>
